//! YAml-ified AST that we use for testing because we're too lazy to write a
//! proper parser now.

use std::collections::HashMap;

use serde::Deserialize;
use thiserror::Error;

use stencil_util::{check_dups_by, FieldName, IdentString, TypeName};

use stencil_layout::{
    parse_basic_type_str, ArrayShape, FieldOp, FieldReadOp, FieldType, FieldWriteOp, ModTypedef,
    ModTypedefEntry, ModuleDef, StructBufTd, StructBufTdField,
};
use stencil_tysys::{UserTypeId, ValueType};

use crate::YaError;

#[derive(Clone, Debug, Deserialize)]
pub struct YaastModule {
    ents: Vec<YaDef>,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "ty", rename_all = "lowercase")]
pub enum YaDef {
    Struct(YaStructDef),
}

impl YaDef {
    pub fn name(&self) -> Option<&str> {
        match self {
            Self::Struct(s) => Some(&s.name),
        }
    }
}

#[derive(Clone, Debug, Deserialize)]
pub struct YaStructDef {
    name: String,
    fields: Vec<YaStructFieldDef>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct YaStructFieldDef {
    name: String,
    ty: String,
}

struct YaastParseState {
    ty_id_map: HashMap<String, UserTypeId>,
}

impl YaastParseState {
    fn new(ty_id_map: HashMap<String, UserTypeId>) -> Self {
        Self { ty_id_map }
    }

    fn get_type_id(&self, name: &str) -> Option<UserTypeId> {
        self.ty_id_map.get(name).copied()
    }

    /// Resolves a type name, including nontrivial types such as arrays.
    fn resolve_type_name(&self, name: &str) -> Result<FieldType, YaError> {
        if let Some(id) = self.ty_id_map.get(name) {
            return Ok(FieldType::Struct(*id));
        }

        // Parse arrays.
        if name.starts_with("[") && name.ends_with("]") && name.contains(";") {
            let inner = name.strip_prefix("[").unwrap().strip_suffix("]").unwrap();

            // Use rsplit here to handle multi-level arrays.
            let (left, right) = inner
                .trim()
                .rsplit_once(";")
                .ok_or_else(|| YaError::UnknownType(name.to_owned()))?;

            let array_len = right
                .trim()
                .parse::<usize>()
                .map_err(|_| YaError::UnknownType(name.to_owned()))?;
            let array_ty = self
                .resolve_type_name(left.trim())
                .map_err(|_| YaError::UnknownType(name.to_owned()))?;

            return Ok(FieldType::Array(array_len, Box::new(array_ty)));
        }

        // Handle inner types.
        Ok(parse_basic_type_str(name).ok_or_else(|| YaError::UnknownType(name.to_owned()))?)
    }
}

pub fn convert_to_mod_layout(m: &YaastModule) -> Result<ModuleDef, YaError> {
    // First, check for duplicates.
    if let Some(dup) = check_dups_by(m.ents.iter().filter(|e| e.name().is_some()), |e| {
        e.name().unwrap()
    }) {
        return Err(YaError::DuplicateTypeName(dup.to_owned()));
    }

    let ty_id_map = gen_struct_ids(m);
    let state = YaastParseState::new(ty_id_map);
    let mut entries = Vec::new();

    for ent in &m.ents {
        match ent {
            YaDef::Struct(ysdef) => {
                let id = state
                    .get_type_id(&ysdef.name)
                    .expect("yaast: missing struct type ID");

                let td = convert_struct_def(ysdef, m, &state)?;
                let mtd = ModTypedef::StructBuf(td);

                entries.push(ModTypedefEntry::new(id, mtd));
            }
        }
    }

    Ok(ModuleDef::new(entries))
}

fn convert_struct_def(
    ysdef: &YaStructDef,
    _m: &YaastModule,
    state: &YaastParseState,
) -> Result<StructBufTd, YaError> {
    // Check for duplicates here to.
    if let Some(dup) = check_dups_by(ysdef.fields.iter(), |e| &e.name) {
        return Err(YaError::DuplicateFieldName(dup.clone()));
    }

    let tname = TypeName::from(IdentString::new_from_snake(ysdef.name.clone())?);
    let mut total_size = 32; // FIXME compute this properly
    let mut fields = Vec::new();
    for fdef in &ysdef.fields {
        let fty = state.resolve_type_name(&fdef.ty)?;

        let ty: ValueType = (&fty).into();

        let fname = FieldName::from(IdentString::new_from_snake(fdef.name.clone())?);

        let ops = vec![
            FieldOp::Read(FieldReadOp::Get),
            FieldOp::Write(FieldWriteOp::Set),
        ];

        let fdef = StructBufTdField::new(fname, ty, ArrayShape::new_unit(), 0, ops);
        fields.push(fdef);
    }

    Ok(StructBufTd::new(tname, total_size, fields))
}

fn gen_struct_ids(m: &YaastModule) -> HashMap<String, UserTypeId> {
    let mut i = 1;
    m.ents
        .iter()
        .filter_map(|def| {
            def.name().map(|s| {
                let ii = i;
                i += 1;
                (s.to_owned(), UserTypeId::from_raw(ii))
            })
        })
        .collect()
}
